#include "qelib.h"
#include "yaffscfg.h"
#include "yaffs_guts.h"
#include "yaffsfs.h"
#include "yaffs_trace.h"
#include "yaffs_packedtags2.h"
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>


#define TEST_LOG_DOMAIN     "yaffs-test"
#define test_debug(...)     qelog_debug(TEST_LOG_DOMAIN, __VA_ARGS__)
#define test_info(...)      qelog_info(TEST_LOG_DOMAIN, __VA_ARGS__)
#define test_notice(...)    qelog_notice(TEST_LOG_DOMAIN, __VA_ARGS__)
#define test_warning(...)   qelog_warning(TEST_LOG_DOMAIN, __VA_ARGS__)
#define test_error(...)     qelog_error(TEST_LOG_DOMAIN, __VA_ARGS__)
#define test_hexdump(...)   qehex_debug(TEST_LOG_DOMAIN, __VA_ARGS__)


#define MAX_FILES           (4)
#define PAGES_PER_BLOCK     (16)
#define BLOCKS_PER_FILE     (8)
#define NUM_BLOCKS          (MAX_FILES * BLOCKS_PER_FILE)
#define PAGE_DATA_SIZE      (2048)
#define PAGE_SPARE_SIZE     (64)
#define PAGE_SIZE           (PAGE_DATA_SIZE + PAGE_SPARE_SIZE)
#define PAGES_PER_FILE      (PAGES_PER_BLOCK * BLOCKS_PER_FILE)
#define BLOCK_SIZE          (PAGES_PER_BLOCK * PAGE_SIZE)
#define FILE_SIZE           (BLOCK_SIZE * BLOCKS_PER_FILE)



typedef struct 
{
    int fds[MAX_FILES];
    int num_blocks;
} filedisk_device;

static filedisk_device filedisk;

unsigned yaffs_trace_mask =

	YAFFS_TRACE_SCAN |
	YAFFS_TRACE_GC |
	YAFFS_TRACE_ERASE |
	YAFFS_TRACE_ERROR |
	YAFFS_TRACE_TRACING |
	YAFFS_TRACE_ALLOCATE |
	YAFFS_TRACE_BAD_BLOCKS |
	YAFFS_TRACE_VERIFY |
    YAFFS_TRACE_NANDACCESS |
	0;

static int filedisk_write_chunk(struct yaffs_dev *dev, int nand_chunk, 
    const u8 *data, int data_len,
    const u8 *oob, int oob_len)
{
    int n;
    int fd;
    int pos;
    int ret = YAFFS_OK;

    (void)dev;

    fd = filedisk.fds[nand_chunk / PAGES_PER_FILE];

    if (data && data_len) {
        pos = (nand_chunk % PAGES_PER_FILE) * PAGE_SIZE;
        lseek(fd, pos, SEEK_SET);
        n = write(fd, data, data_len);
        if (n != data_len) {
            test_error("chunk %d write data error", nand_chunk);
            ret = YAFFS_FAIL;
        } else {
            test_debug("chunk %d fd %d write data pos:%d len:%d", 
                nand_chunk, fd, pos, data_len);
        }
    }

    if (oob && oob_len) {
        pos = (nand_chunk % PAGES_PER_FILE) * PAGE_SIZE + PAGE_DATA_SIZE;
        lseek(fd, pos, SEEK_SET);
        n = write(fd, oob, oob_len);
        if (n != oob_len) {
            test_error("chunk %d write oob error", nand_chunk);
            ret = YAFFS_FAIL;
        } else {
            test_debug("chunk %d fd %d write oob pos:%d len:%d", 
                nand_chunk, fd, pos, oob_len);
            test_hexdump(oob, oob_len);
        }
    }

    return ret;
}

static int filedisk_read_chunk(struct yaffs_dev *dev, int nand_chunk,
	u8 *data, int data_len,
	u8 *oob, int oob_len,
	enum yaffs_ecc_result *ecc_result)
{
    int n;
    int fd;
    int pos;
    int ret = YAFFS_OK;

    (void)dev;
    *ecc_result = YAFFS_ECC_RESULT_NO_ERROR;

    fd = filedisk.fds[nand_chunk / PAGES_PER_FILE];

    if (data && data_len) {
        pos = (nand_chunk % PAGES_PER_FILE) * PAGE_SIZE;
        lseek(fd, pos, SEEK_SET);
        n = read(fd, data, data_len);
        if (n != data_len) {
            test_error("chunk %d fd %d read data pos:%d len:%d error:%d", 
                nand_chunk, fd, pos, data_len, n);
            ret = YAFFS_FAIL;
        }
        test_debug("chunk %d fd %d read data pos:%d len:%d", 
            nand_chunk, fd, pos, data_len);
    }

    if (oob && oob_len) {
        pos = (nand_chunk % PAGES_PER_FILE) * PAGE_SIZE + PAGE_DATA_SIZE;
        lseek(fd, pos, SEEK_SET);
        n = read(fd, oob, oob_len);
        if (n != oob_len) {
            test_error("chunk %d fd:%d read oob pos:%d len:%d error:%d", 
                nand_chunk, fd, pos, oob_len, n);
            ret = YAFFS_FAIL;
        }
        test_debug("chunk %d fd %d read oob pos:%d len:%d", 
            nand_chunk, fd, pos, oob_len);
        test_hexdump(oob, oob_len);
    }

    return ret;
}

static int filedisk_erase_chunk(struct yaffs_dev *dev, int block_no)
{
    int i;
    int fd;
    int pos;

    test_debug("chunk %d erase", block_no);

    if (block_no < 0 || block_no >= NUM_BLOCKS) {
        test_error("block %d out of range(0, %d)", NUM_BLOCKS - 1);
        return YAFFS_FAIL;
    }

    u8 pg[PAGE_SIZE];
    int syz = PAGE_SIZE;

    qe_memset(pg, 0xFF, syz);
    fd = filedisk.fds[block_no / BLOCKS_PER_FILE];
    pos = ((block_no % BLOCKS_PER_FILE) * PAGES_PER_BLOCK) * PAGE_SIZE;
    lseek(fd, pos, SEEK_SET);

    for (i=0; i<PAGES_PER_BLOCK; i++) {
        write(fd, pg, PAGE_SIZE);
        test_debug("fd %d write pg pos:%d", fd, pos);
    }

    return YAFFS_OK;
}

static int filedisk_mark_bad(struct yaffs_dev *dev, int block_no)
{
    int n;
    int fd;
    int pos;
    struct yaffs_packed_tags2 pt;
    test_info("chunk %d mark bad", block_no);
    qe_memset(&pt, 0, sizeof(pt));
    fd = filedisk.fds[block_no / BLOCKS_PER_FILE];
    pos = ((block_no % BLOCKS_PER_FILE) * PAGES_PER_BLOCK) * PAGE_SIZE + PAGE_DATA_SIZE;
    lseek(fd, pos, SEEK_SET);
    n = write(fd, &pt, sizeof(pt));
    if (n != sizeof(pt)) {
        test_error("mark bad block %d error", block_no);
        return YAFFS_FAIL;
    }
    test_debug("fd %d write pt pos:%d", fd, pos);
    return YAFFS_OK;
}

static int filedisk_check_bad(struct yaffs_dev *dev, int block_no)
{
	(void) dev;
	(void) block_no;
    test_debug("chunk %d check bad", block_no);
	return YAFFS_OK;
}

static int filedisk_fd_init(int n, qe_gbuf *dummy)
{
    int i;
    int fd;
    int size;
    char name[64];

    qe_sprintf(name, "filedisk-%d.bin", n);

    fd = open(name, O_RDWR|O_CREAT|O_BINARY, S_IREAD | S_IWRITE);
    if (fd >= 0) {
        size = lseek(fd, 0, SEEK_END);
        if (size < FILE_SIZE) {
            for (i=0; i<BLOCKS_PER_FILE; i++) {
                if (write(fd, qe_gbuf_pos(dummy), qe_gbuf_size(dummy)) != BLOCK_SIZE)
                    return -1;
            }
        }
        test_info("file %d fd:%d %s init", n, fd, name);
    } else {
        test_error("%s open error", name);
    }

    return fd;
}

static int filedisk_initialise(struct yaffs_dev *dev)
{
    int i;
    int seed;
    qe_gbuf *dummy;
    srand(seed);
    test_debug("filedisk initialise in");
    dummy = qe_gbuf_new(BLOCK_SIZE);
    filedisk.num_blocks = NUM_BLOCKS;
    qe_memset(qe_gbuf_pos(dummy), 0xFF, qe_gbuf_size(dummy));

    for(i=0; i<MAX_FILES; i++) {
        filedisk.fds[i] = filedisk_fd_init(i, dummy);
        if (filedisk.fds[i] < 0) {
            qe_free(dummy);
            return YAFFS_FAIL;
        }
    }
    qe_free(dummy);
    test_info("filedisk initialise success, %d fds init", MAX_FILES);
    return YAFFS_OK;
}

static qe_ret fs_drv_install(qe_const_str name)
{
    struct yaffs_dev *dev;
    struct yaffs_param *param;
    struct yaffs_driver *drv;

    dev = qe_malloc(sizeof(struct yaffs_dev));
    if (!dev) {
        test_error("alloc dev error");
        return qe_err_mem;
    }
    qe_memset(dev, 0, sizeof(struct yaffs_dev));

    dev->param.name = qe_strdup(name);

    drv = &dev->drv;
    drv->drv_write_chunk_fn = filedisk_write_chunk;
    drv->drv_read_chunk_fn  = filedisk_read_chunk;
    drv->drv_erase_fn       = filedisk_erase_chunk;
    drv->drv_mark_bad_fn    = filedisk_mark_bad;
    drv->drv_check_bad_fn   = filedisk_check_bad;
    drv->drv_initialise_fn  = filedisk_initialise;

    param = &dev->param;
    param->total_bytes_per_chunk = PAGE_DATA_SIZE;
    param->chunks_per_block      = PAGES_PER_BLOCK;
    param->start_block           = 0;
    param->end_block             = NUM_BLOCKS - 2;
    param->is_yaffs2             = 1;
    param->use_nand_ecc          = 0;
    param->n_reserved_blocks     = 2;
    param->wide_tnodes_disabled  = 0;
    param->refresh_period        = 1000;
    param->n_caches              = 10; // Use caches
    param->enable_xattr          = 1;

    yaffs_add_device(dev);

    test_info("yaffs dev %s add", name);

    return qe_ok;
}

static void format_test(qe_const_str path)
{
    int ret;

    ret = yaffs_format(path, 0, 0, 0);
    test_info("yaffs_format(0, 0, 0) of unmounted ret:%d"
        "should return 0", ret);

    ret = yaffs_mount(path);
    test_info("yaffs_mount() ret:%d", ret);

    ret = yaffs_format(path, 0, 0, 0);
    test_info("yaffs_format(0, 0, 0) of mounted ret:%d"
        "should return -1(busy)", ret);

    ret = yaffs_format(path, 1, 0, 0);
    test_info("yaffs_format(1, 0, 0) of mounted ret:%d"
        "should return 0", ret);

    ret = yaffs_mount(path);
    test_info("yaffs_mount() ret:%d"
        "should return 0", ret);

    ret = yaffs_format(path, 1, 0, 1);
    test_info("yaffs_format(1, 0, 1) of mounted ret:%d"
        "should return 0", ret);

    ret = yaffs_mount(path);
    test_info("yaffs_mount() ret:%d"
        "should return -1", ret);
}

void dump_directory_tree_worker(const char *dname,int recursive)
{
	yaffs_DIR *d;
	struct yaffs_dirent *de;
	struct yaffs_stat s;
	char str[1000];

	d = yaffs_opendir(dname);

	if(!d)
	{
		printf("opendir failed\n");
	}
	else
	{
		while((de = yaffs_readdir(d)) != NULL)
		{
			sprintf(str,"%s/%s",dname,de->d_name);

			yaffs_lstat(str,&s);

			printf("%s -- inode %d length %d mode %X ",
				str, s.st_ino, (int)s.st_size, s.st_mode);
			switch(s.st_mode & S_IFMT)
			{
				case S_IFREG: printf("data file"); break;
				case S_IFDIR: printf("directory"); break;
				case S_IFLNK: printf("symlink -->");
							  if(yaffs_readlink(str,str,100) < 0)
								printf("no alias");
							  else
								printf("\"%s\"",str);
							  break;
				default: printf("unknown"); break;
			}

			printf("\n");

			if((s.st_mode & S_IFMT) == S_IFDIR && recursive)
				dump_directory_tree_worker(str,1);

		}

		yaffs_closedir(d);
	}

}

void dumpDir(const char *dname)
{	dump_directory_tree_worker(dname,0);
	printf("\n");
	printf("Free space in %s is %d\n\n",dname,(int)yaffs_freespace(dname));
}

void make_pattern_file(char *fn,int size)
{
	int outh;
	int marker;
	int i;
	outh = yaffs_open(fn, O_CREAT | O_RDWR | O_TRUNC, S_IREAD | S_IWRITE);
	yaffs_lseek(outh,size-1,SEEK_SET);
	yaffs_write(outh,"A",1);

	for(i = 0; i < size; i+=256)
	{
		marker = ~i;
		yaffs_lseek(outh,i,SEEK_SET);
		yaffs_write(outh,&marker,sizeof(marker));
	}
	yaffs_close(outh);

}

int check_pattern_file(char *fn)
{
	int h;
	int marker;
	int i;
	int size;
	int ok = 1;

	h = yaffs_open(fn, O_RDWR,0);
	size = yaffs_lseek(h,0,SEEK_END);

	for(i = 0; i < size; i+=256)
	{
		yaffs_lseek(h,i,SEEK_SET);
		yaffs_read(h,&marker,sizeof(marker));
		ok = (marker == ~i);
		if(!ok)
		{
		   printf("pattern check failed on file %s, size %d at position %d. Got %x instead of %x\n",
					fn,size,i,marker,~i);
		}
	}
	yaffs_close(h);
	return ok;
}

int dump_file_data(char *fn)
{
	int h;
	int i = 0;
	int ok = 1;
	unsigned char b;

	h = yaffs_open(fn, O_RDWR,0);


	printf("%s\n",fn);
	while(yaffs_read(h,&b,1)> 0)
	{
		printf("%02x",b);
		i++;
		if(i > 32)
		{
		   printf("\n");
		   i = 0;;
		 }
	}
	printf("\n");
	yaffs_close(h);
	return ok;
}

static void long_name_test(qe_const_ptr path)
{
    int i;
    int f;
    int result;
    char full_name[1000];
    char name[300];

    qe_memset(name, 0, sizeof(name));
    for (i=0; i<256; i++) {
        name[i] = '0' + i % 10;
    }
    qe_sprintf(full_name, "%s/%s", path, name);

    for (i=0; i<1; i++) {

        yaffs_mount(path);

        test_info("Files at start");
        dumpDir(path);

        test_info("Creating file %s",full_name);

        f = yaffs_open(full_name, O_CREAT | O_RDWR, 0);
		yaffs_close(f);

        test_info("Result %d", f);

		test_info("Files");
		dumpDir(path);

		test_info("Deleting %s",full_name);
		result = yaffs_unlink(full_name);
		test_info("Result %d",result);

		test_info("Files\n");

		dumpDir(path);

		yaffs_unmount(path);
    }
}

void link_test0(qe_const_str mountpt)
{
	char namea[300];
	char nameb[300];
	int result = 0;

	yaffs_mount(mountpt);


	sprintf(namea,"%s/a",mountpt);
	sprintf(nameb,"%s/b",mountpt);

	printf("mounted\n");
        dumpDir(mountpt);

	yaffs_unlink(namea);
	printf("a unlinked\n");
        dumpDir(mountpt);

	yaffs_unlink(nameb);
	printf("b unlinked\n");
        dumpDir(mountpt);

	result = yaffs_open(namea,O_CREAT| O_RDWR,0666);
        yaffs_close(result);
	printf("a created\n");
        dumpDir(mountpt);

        yaffs_link(namea,nameb);
        printf("a linked b\n");
        dumpDir(mountpt);
        yaffs_unlink(namea);
        printf("a ulinked b\n");
        dumpDir(mountpt);
        yaffs_unlink(nameb);
        printf("b unlinked\n");
        dumpDir(mountpt);

	yaffs_unmount(mountpt);
}

void scan_pattern_test(qe_const_ptr path, int fsize, int niterations)
{
	int i;
	int j;
	char fn[3][100];
	int result;

	qe_sprintf(fn[0],"%s/%s",path,"f0");
	qe_sprintf(fn[1],"%s/%s",path,"f1");
	qe_sprintf(fn[2],"%s/%s",path,"f2");

	for(i = 0; i < niterations; i++)
	{
        printf("\n*****************\nIteration %d\n",i);
		yaffs_mount(path);
		printf("\nmount: Directory look-up of %s\n",path);
		dumpDir(path);
		for(j = 0; j < 3; j++)
		{
			result = dump_file_data(fn[j]);
			result = check_pattern_file(fn[j]);
			make_pattern_file(fn[j],fsize);
			result = dump_file_data(fn[j]);
			result = check_pattern_file(fn[j]);
			if (result)
				printf("result in line %d is %d", __LINE__, result);
		}
		yaffs_unmount(path);
	}
}

static void copy_in_a_file(const char *yaffsName,const char *inName)
{
	int inh,outh;
	unsigned char buffer[100];
	int ni,no;
	inh = open(inName,O_RDONLY|O_BINARY);
	outh = yaffs_open(yaffsName, O_CREAT | O_RDWR | O_TRUNC, S_IREAD | S_IWRITE);

	while((ni = read(inh,buffer,100)) > 0)
	{
		no = yaffs_write(outh,buffer,ni);
		if(ni != no)
		{
			printf("problem writing yaffs file\n");
		}
	}

	yaffs_close(outh);
	close(inh);
}

static void long_test(void)
{
    int f, r;
    char buffer[20];
    struct yaffs_stat ystat;

    yaffs_mount("/nand");

    dumpDir("/nand");

    f = yaffs_open("/nand/b1", O_RDONLY, 0);
    test_info("open /nand/b1 readonly, f=%d",f);

    f = yaffs_open("/nand/b1", O_CREAT,S_IREAD | S_IWRITE);
    test_info("open /nand/b1 O_CREAT, f=%d",f);

    r = yaffs_write(f,"hello",1);
    test_info("write %d attempted to write to a read-only file",r);

    r = yaffs_close(f);
    test_info("close %d",r);

    f = yaffs_open("/nand/b1", O_RDWR,0);
    test_info("open /nand/b1 O_RDWR,f=%d",f);

	r = yaffs_write(f,"hello",2);
	test_info("write %d attempted to write to a writeable file",r);
	r = yaffs_write(f,"world",3);
	test_info("write %d attempted to write to a writeable file",r);

    r= yaffs_lseek(f,0,SEEK_END);
	test_info("seek end %d",r);
	memset(buffer,0,20);
	r = yaffs_read(f,buffer,10);
	test_info("read %d \"%s\"",r,buffer);
	r= yaffs_lseek(f,0,SEEK_SET);
	test_info("seek set %d",r);
	memset(buffer,0,20);
	r = yaffs_read(f,buffer,10);
	test_info("read %d \"%s\"",r,buffer);
	memset(buffer,0,20);
	r = yaffs_read(f,buffer,10);
	test_info("read %d \"%s\"",r,buffer);

	r= yaffs_lseek(f,0,SEEK_END);
	r = yaffs_read(f,buffer,10);
	test_info("read at end returned  %d",r);
	r= yaffs_lseek(f,500,SEEK_END);
	r = yaffs_read(f,buffer,10);
	test_info("read past end returned  %d",r);

    r = yaffs_close(f);

    test_info("close %d",r);

    copy_in_a_file("/nand/yyfile","sdtrace.bin");
    copy_in_a_file("/nand/file with a long name","sdtrace.bin");
    
	test_info("\nDirectory look-up of /nand");
	dumpDir("/nand");

    r = yaffs_lstat("/nand/file with a long name",&ystat);
    test_info("size:%d", ystat.st_size);
    r = yaffs_rename("/nand/file with a long name","/nand/r1");

	test_info("\nDirectory look-up of /nand");
	dumpDir("/nand");

    r = yaffs_unlink("/nand/r1");

	test_info("\nDirectory look-up of /nand");
	dumpDir("/nand");

    r = yaffs_mkdir("/nand/directory1",0);

	test_info("\nDirectory look-up of /nand");
	dumpDir("/nand");
	test_info("\nDirectory look-up of /nand/directory1");
	dumpDir("/nand/directory1");

    copy_in_a_file("/nand/directory1/file with a long name","sdtrace.bin");

	test_info("\nDirectory look-up of /nand");
	dumpDir("/nand");
	test_info("\nDirectory look-up of /nand/directory1");
	dumpDir("/nand/directory1");
}

static void dir_test()
{
    int r;

    yaffs_mount("/nand");

    r = yaffs_mkdir("/nand/test", 0);
    test_info("mkdir /nand/test ret:%d", r);

    dumpDir("/nand");
}

static void oob_gatter(qe_u8 *spare_buffer, qe_u8 *oob, qe_uint len)
{
    qe_int copy_len;
    while (len) {
        copy_len = qe_min(len, 8);
        qe_memcpy(oob, spare_buffer, copy_len);
        oob += copy_len;
        spare_buffer += 16;
        len -= copy_len;
    }
}

static void oob_scatter(qe_u8 *oob, qe_uint len, qe_u8 *spare_buffer)
{
    qe_int copy_len;

    while (len) {
        copy_len = qe_min(len, 8);
        qe_memcpy(spare_buffer, oob, copy_len);
        oob += copy_len;
        spare_buffer += 16;
        len -= copy_len;
    }
}

static void oob_gatter_scatter_test()
{
    int i;
    qe_u8 spare_buffer[64];
    qe_u8 oob[28], roob[28];

    qe_memset(oob, 0, sizeof(oob));
    qe_memset(roob, 0, sizeof(roob));
    qe_memset(spare_buffer, 0, sizeof(spare_buffer));
    
    for (i=0; i<28; i++) {
        oob[i] = i + 0x80;
    }
    test_info("oob:");
    qehex_info("oob", oob, sizeof(oob));
    oob_scatter(oob, sizeof(oob), spare_buffer);
    test_info("scatter:");
    qehex_info("oob", spare_buffer, sizeof(spare_buffer));
    oob_gatter(spare_buffer, roob, sizeof(roob));
    test_info("gatter:");
    qehex_info("oob", roob, sizeof(roob));
}

int main(int argc, char *argv[])
{

    qelog_init(QELOG_INFO, QELOG_CL|QELOG_DM|QELOG_DATE|QELOG_LV);
    qelog_domain_set_level("yaffs-test", QELOG_INFO);
    fs_drv_install("nand");

    format_test("/nand");
    //long_name_test("/nand");
    //link_test0("/nand");
    //scan_pattern_test("/nand", 10000, 10);
    //long_test();

    //dir_test();
    oob_gatter_scatter_test();
    return 0;
}